#ifndef SPSC_QUEUE_H
#define SPSC_QUEUE_H

#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#ifndef SPSC_QUEUE_DMB
#define SPSC_QUEUE_DMB() ((void)0)
#endif

#define SPSC_QUEUE_OK (1)
#define SPSC_QUEUE_FAIL (0)

static inline uint32_t SpscQueue_Min(uint32_t a, uint32_t b)
{
    uint32_t result = a;
    if (b < a)
    {
        result = b;
    }
    return result;
}

struct SpscQueue
{
    uint32_t size;
    uint32_t mask;
    volatile uint32_t head;
    volatile uint32_t tail;
    uint8_t *buffer;
};

static inline struct SpscQueue *SpscQueue_Create(uint32_t size)
{
    struct SpscQueue *q = NULL;
    uint8_t *buf = NULL;
    struct SpscQueue *result = NULL;
    uint32_t valid_size = 0U;

    if ((size != 0U) && ((size & (size - 1U)) == 0U))
    {
        valid_size = 1U;
    }
    else
    {
        valid_size = 0U;
    }

    if (valid_size == 1U)
    {
        q = (struct SpscQueue *)malloc(sizeof(struct SpscQueue));
        if (q != NULL)
        {
            buf = (uint8_t *)malloc(size);
            if (buf != NULL)
            {
                q->buffer = buf;
                q->size = size;
                q->mask = size - 1U;
                q->head = 0U;
                q->tail = 0U;
                result = q;
            }
            else
            {
                free(q);
                result = NULL;
            }
        }
        else
        {
            result = NULL;
        }
    }
    else
    {
        result = NULL;
    }
    return result;
}

static inline void SpscQueue_Destroy(struct SpscQueue *q)
{
    if (q != NULL)
    {
        if (q->buffer != NULL)
        {
            free(q->buffer);
            q->buffer = NULL;
        }
        free(q);
    }
}

static inline int32_t SpscQueue_Push(struct SpscQueue *q, const uint8_t *data, uint32_t len)
{
    int32_t result = SPSC_QUEUE_FAIL;
    uint32_t tail = 0U;
    uint32_t head = 0U;
    uint32_t pos = 0U;
    uint32_t first = 0U;

    if ((q != NULL) && (data != NULL) && (len != 0U))
    {
        tail = q->tail;
        head = q->head;
        if ((q->size - (tail - head)) >= len)
        {
            pos = tail & q->mask;
            first = SpscQueue_Min(len, q->size - pos);
            (void)memcpy(&q->buffer[pos], data, first);
            if (first < len)
            {
                (void)memcpy(q->buffer, &data[first], len - first);
            }
            SPSC_QUEUE_DMB();
            q->tail = tail + len;
            result = SPSC_QUEUE_OK;
        }
        else
        {
            result = SPSC_QUEUE_FAIL;
        }
    }
    else
    {
        result = SPSC_QUEUE_FAIL;
    }

    return result;
}

static inline uint32_t SpscQueue_Pop(struct SpscQueue *q, uint8_t *data, uint32_t len)
{
    uint32_t tail = 0U;
    uint32_t head = 0U;
    uint32_t avail = 0U;
    uint32_t pos = 0U;
    uint32_t first = 0U;
    uint32_t ret_len = 0U;

    if ((q != NULL) && (data != NULL) && (len != 0U))
    {
        tail = q->tail;
        head = q->head;
        avail = tail - head;
        if (avail != 0U)
        {
            if (len > avail)
            {
                len = avail;
            }
            pos = head & q->mask;
            first = SpscQueue_Min(len, q->size - pos);

            (void)memcpy(data, &q->buffer[pos], first);
            if (first < len)
            {
                (void)memcpy(&data[first], q->buffer, len - first);
            }
            SPSC_QUEUE_DMB();
            q->head = head + len;
            ret_len = len;
        }
        else
        {
            ret_len = 0U;
        }
    }
    else
    {
        ret_len = 0U;
    }
    return ret_len;
}

static inline uint32_t SpscQueue_DataLen(const struct SpscQueue *q)
{
    uint32_t result = 0U;
    if (q != NULL)
    {
        result = (q->tail - q->head);
    }
    else
    {
        result = 0U;
    }
    return result;
}

#endif /* SPSC_QUEUE_H */